﻿
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace MyFramework.Common
{
    /// <summary>
    /// Validator provider
    /// </summary>
    public class ValidationProvider : IValidationProvider
    {
        /// <summary>
        /// The instance
        /// </summary>
        private INotifyPropertyChanged instance;

        /// <summary>
        /// The validators
        /// </summary>
        public readonly Dictionary<string, ValidationAttribute[]> validators;

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationProvider" /> class.
        /// </summary>
        /// <param name="instance">The instance.</param>
        public ValidationProvider(INotifyPropertyChanged instance)
        {
            this.instance = instance;

            this.validators = instance.GetType().GetProperties()
                            .Where(p => GetValidations(p).Length != 0)
                            .ToDictionary(p => p.Name, p => GetValidations(p));
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public string Error
        {
            get
            {
                var errors = from i in this.validators
                             from v in i.Value
                             where v.GetValidationResult(GetValue(i.Key), new ValidationContext(this.instance, null, null) { MemberName = i.Key }) != ValidationResult.Success
                             select v.ErrorMessage;
                return string.Join(Environment.NewLine, errors.ToArray());
            }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <returns>
        /// The error message for the property. The default is an empty string ("").
        /// </returns>
        public string this[string columnName]
        {
            get
            {
                if (HasProperty(columnName) && this.validators.ContainsKey(columnName))
                {
                    var value = GetValue(columnName);
                    ValidationContext context = new ValidationContext(this.instance, null, null) { MemberName = columnName };
                    IEnumerable<ValidationAttribute> errors = this.validators[columnName].Where(v => v.GetValidationResult(value, context) != ValidationResult.Success);
                    IEnumerable<ValidationAttribute> notValidAttributes = errors.Where(x => x.ErrorMessage == null);
                    foreach (var attribute in notValidAttributes)
                    {
                        SetDefaultErrorMessage(attribute, context);
                    }

                    //this.RaisePropertyChangedEvent(() => this.Error);
                    return string.Join(Environment.NewLine, errors.Select(v => v.ErrorMessage).ToArray());
                }

                //this.RaisePropertyChangedEvent(() => this.Error);
                return string.Empty;
            }
        }

        /// <summary>
        /// Sets the default error message.
        /// </summary>
        /// <param name="attribute">The attribute.</param>
        /// <param name="context">The context.</param>
        private void SetDefaultErrorMessage(ValidationAttribute attribute, ValidationContext context)
        {
            Type type = attribute.GetType();
            if (type == typeof(RequiredAttribute))
            {
                attribute.ErrorMessage = string.Format("This field is required");
            }
            else if (type == typeof(DataTypeAttribute))
            {
                attribute.ErrorMessage = string.Format("This field is not of {0} type", context.ObjectType.GetProperty(context.MemberName).GetType().Name);
            }
            else if (type == typeof(RangeAttribute))
            {
                attribute.ErrorMessage = string.Format("This field is not within {0} and {1}", ((RangeAttribute)attribute).Minimum, ((RangeAttribute)attribute).Maximum);
            }
            else if (type == typeof(RegularExpressionAttribute))
            {
                attribute.ErrorMessage = string.Format("This field doesn't respect the expected format");
            }
            else if (type == typeof(StringLengthAttribute))
            {
                attribute.ErrorMessage = string.Format("This field length is not within {0} and {1}", ((StringLengthAttribute)attribute).MinimumLength, ((StringLengthAttribute)attribute).MaximumLength);
            }
            else
            {
                attribute.ErrorMessage = string.Format("Value not valid");
            }
        }

        /// <summary>
        /// Gets the validations.
        /// </summary>
        /// <param name="property">The property.</param>
        /// <returns></returns>
        private ValidationAttribute[] GetValidations(PropertyInfo property)
        {
            return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
        }

        /// <summary>
        /// Determines whether the specified property name has property.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>
        ///   <c>true</c> if the specified property name has property; otherwise, <c>false</c>.
        /// </returns>
        private bool HasProperty(string propertyName)
        {
            return this.instance.GetType().GetProperty(propertyName) != null;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns></returns>
        private object GetValue(string propertyName)
        {
            return this.instance.GetType().GetProperty(propertyName).GetValue(this.instance, null);
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The name of the changed property.</param>
        protected void RaisePropertyChangedEvent(string propertyName)
        {
            if (PropertyChanged != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="projection">The projection.</param>
        protected virtual void RaisePropertyChangedEvent<TProperty>(Expression<Func<TProperty>> projection)
        {
            RaisePropertyChangedEvent(projection.GetPropertyName());
        }

        #endregion
    }
}
